package com.gill.ftp.util;

import com.gill.ftp.config.properties.FtpProperties;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.net.PrintCommandListener;
import org.apache.commons.net.ftp.*;

import java.io.*;

@Slf4j
public class FtpUtil {

	private static volatile FtpUtil ftpUtil;
	private FtpProperties ftpProperties;
	private FTPClient ftpClient;

	private FtpUtil() {
	}

	public static FtpUtil getInstance(FtpProperties ftpProperties) {
		if(ftpProperties == null){
			throw new RuntimeException("ftpProperties can't not be null");
		}
		if (ftpUtil == null) {
			synchronized (FtpUtil.class) {
				if (ftpUtil == null) {
					ftpUtil = new FtpUtil();
					ftpUtil.ftpProperties = ftpProperties;
					ftpUtil.ftpClient = new FTPClient();
					// 打印ftp的操作到控制台
					ftpUtil.ftpClient.addProtocolCommandListener(new PrintCommandListener(new PrintWriter(System.out)));
				}
			}
		}
		return ftpUtil;
	}

	/**
	 * 连接ftp服务端
	 * @param ftpProperties ftp相关参数
	 */
	private void connect(FtpProperties ftpProperties){
		try {
			ftpClient.connect(ftpProperties.getHost(), ftpProperties.getPort());
			ftpClient.login(ftpProperties.getUsername(), ftpProperties.getPassword());
			//设置文件编码格式
			ftpClient.setControlEncoding(ftpProperties.getEncoding());
			//ftp通信有两种模式（主动、被动，为了安全一般设置为被动）
			ftpClient.enterLocalPassiveMode();
			//设置传输方式为流方式
			ftpClient.setFileTransferMode(FTP.STREAM_TRANSFER_MODE);
		}catch (IOException e){
			throw new RuntimeException("连接FTP服务器出错");
		}
		//获取状态码，判断是否连接成功
		if(!FTPReply.isPositiveCompletion(ftpClient.getReplyCode())) {
			throw new RuntimeException("FTP服务器拒绝连接");
		}
	}

	/**
	 * 上传文件（上传后的文件名：baseDir/path/filename）
	 * @param remotePath  上传到ftp服务器的文件目录
	 * @param filename    上传到ftp服务器后文件的命名
	 * @param input       待上传文件的输入流
	 * @return
	 */
	public boolean uploadFile(String remotePath,String filename,InputStream input) {
		// 规范：由于抛出的异常都是IOException，所以可以使用try包在一起
		connect(ftpProperties);
		try {
			//转到上传文件的根目录
			if(!ftpClient.changeWorkingDirectory(ftpProperties.getBaseDir())) {
				throw new RuntimeException("根目录不存在，需要创建");
			}
			//判断是否存在目录
			if(!ftpClient.changeWorkingDirectory(remotePath)) {
				String[] dirs=remotePath.split("/");
				//创建目录
				for (String dir : dirs) {
					if(dir==null || "".equals(dir)){
						continue;
					}
					//判断是否存在目录
					if(!ftpClient.changeWorkingDirectory(dir)) {
						//不存在则创建
						if(!ftpClient.makeDirectory(dir)) {
							throw new RuntimeException("子目录创建失败");
						}
						//进入新创建的目录
						ftpClient.changeWorkingDirectory(dir);
					}
				}
				//设置上传文件的类型为二进制类型
				ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
				//上传文件
				if(!ftpClient.storeFile(filename, input)) {
					return false;
				}
				input.close();
				ftpClient.logout();
				return true;
			}
		} catch (IOException e) {
			throw new RuntimeException(e);
		}finally {
			if(ftpClient.isConnected()) {
				try {
					ftpClient.disconnect();
				} catch (IOException e) {
					throw new RuntimeException(e);
				}
			}
		}
		return false;
	}

	/**
	 * 下载文件
	 * @param filename    文件名，注意！此处文件名为加路径文件名，如：/2015/06/04/aa.jpg
	 * @param localPath    存放到本地地址
	 * @return
	 */
	public boolean downloadFile(String filename,String localPath) {
		// 规范：由于抛出的异常都是IOException，所以可以使用try包在一起
		connect(ftpProperties);
		try {
			int index=filename.lastIndexOf("/");
			//获取文件的路径
			String path=filename.substring(0, index);
			//获取文件名
			String name=filename.substring(index+1);
			//判断是否存在目录
			if(!ftpClient.changeWorkingDirectory(ftpProperties.getBaseDir()+path)) {
				throw new RuntimeException("文件路径不存在："+ftpProperties.getBaseDir()+path);
			}
			//获取该目录所有文件
			FTPFile[] files=ftpClient.listFiles();
			boolean fileExist = false;
			for (FTPFile file : files) {
				//判断是否有目标文件,如果找到，将目标文件下载到本地
				if(!fileExist && file.getName().equals(name)) {
					fileExist = true;
					File localFile =new File(localPath+"/"+file.getName());
					OutputStream out=new FileOutputStream(localFile);
					ftpClient.retrieveFile(file.getName(), out);
					log.info("文件{}下载完成,",ftpProperties.getBaseDir()+"/"+path+"/"+name);
					out.close();
				}
			}
			ftpClient.logout();
			if(!fileExist){
				log.warn("文件{}不存在,",ftpProperties.getBaseDir()+"/"+path+"/"+name);
				return false;
			}
			return true;
		} catch (IOException e) {
			throw new RuntimeException(e);
		}finally {
			if(ftpClient.isConnected()) {
				try {
					ftpClient.disconnect();
				} catch (IOException e) {
					throw new RuntimeException(e);
				}
			}
		}
	}

	/**
	 * 删除文件
	 * @param filename 要删除的文件路径+文件名 如：/imgs/2015/07/13/aa.jpg
	 * @return
	 */
	public boolean deleteFile(String filename) {
		connect(ftpProperties);
		try {
			int index=filename.lastIndexOf("/");
			//获取文件的路径
			String path=filename.substring(0, index);
			//获取文件名
			String name=filename.substring(index+1);
			//判断是否存在目录,不存在则说明文件存在
			if(!ftpClient.changeWorkingDirectory(ftpProperties.getBaseDir()+path)) {
				return true;
			}
			if(ftpClient.deleteFile(name)) {
				clearDirectory(ftpClient, ftpProperties.getBaseDir(), path);
				ftpClient.logout();
				return true;
			}
			ftpClient.logout();
			return false;
		} catch (IOException e) {
			throw new RuntimeException(e);
		}finally {
			if(ftpClient.isConnected()) {
				try {
					ftpClient.disconnect();
				} catch (IOException e) {
					throw new RuntimeException(e);
				}
			}
		}
	}

	/**
	 * 递归删除目录
	 * @param ftpClient	 操作的ftp客户端
	 * @param basePath
	 * @param path		以path为根，递归清除上面所有空的文件夹，直到出现不为空的文件夹停止，最多清除到basePath结束
	 * @throws IOException
	 */
	private void clearDirectory(FTPClient ftpClient,String basePath,String path) throws IOException {
		//如果路径长度小于2，说明到顶了
		if(path.length()<2) {
			return ;
		}
		//如果当前目录文件数目小于1则删除此目录
		if(ftpClient.listNames(basePath+path).length<1) {
			//删除目录
			System.out.println("删除目录："+basePath+path);
			ftpClient.removeDirectory(basePath+path);
			int index=path.lastIndexOf("/");
			//路径向上一层
			path=path.substring(0, index);
			//继续判断
			clearDirectory(ftpClient, basePath, path);
		}
	}
}
